﻿using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using SharpCrack.HashAlgorithms;
using System.Collections.Generic;

namespace SharpCrack.Application.ConsoleApplication
{
	class Program
	{
		static void Main(string[] args)
		{
			// Show header information
			Console.WriteLine("SharpCrack v1.2");
			Console.WriteLine("Created by Peter Juhasz");
			Console.WriteLine("More information: http://cracker.codeplex.com");
			Console.WriteLine();

			// Show usage information
			if (args.Length == 0 || args[0] == "/?" || args[0] == "-help")
			{
				Console.WriteLine(
					"Usage: sharpcrack [-attack=MODE] [OPTIONS] [-input|-single]\n" +
					" -attack=MODE                           Set attack mode (BruteForce, Wordlist)\n" +
					" -parallel                              Use all cores to search\n" +
					" -algorithm=ALGORITHM                   Hash algorithm\n" +
					" -exponent=N                            Hash candidates N times before matching\n" +
					" -input=PATH                            Input file containing the hashes\n" +
					" -single=HASH                           Single hash cracking\n" +
					" -buffersize=SIZE                       Number of words to buffer for parallel processing\n" +
					" -help                                  Show usage informations\n" +
					"\n" +
					" -charset=CHARSET                       Charset used for brute-force attack ($1, $a, $A, $$)\n" +
					" -length=MAX or MIN-MAX                 Length of generated permutations\n" +
					" -startfrom=STRING                      Set starting permutation\n"+
					"\n" +
					" -wordlist=PATH                         Wordlist used for dictionary attack\n" +
					" -position=LINENUMBER                   Set wordlist start position\n" +
					" -lowercase                             Convert words to lowercase\n" +
					" -uppercase                             Convert words to uppercase\n" +
					" -reverse                               Reverse words\n" +
					" -prefix=PREFIX                         Append prefix\n" +
					" -postfix=POSTFIX                       Append postfix\n" +
					"\n" +
					"Supported algorithms: CRC16, CRC32, MD5, RIPEMD160, SHA1, SHA256, SHA384, SHA512"
				);
				
				return;
			}

			// Initialize cracker engine
			if (args.Contains("-parallel"))
				Cracker = new ParallelHashCracker();
			else 
				Cracker = new SequentialHashCracker();

			// Process arguments
			try
			{
				foreach (string arg in args)
				{
					string[] keyValuePair = arg.Split('=');

					switch (keyValuePair[0].ToLower())
					{
						// Settings
						case "-algorithm": Cracker.Algorithm = GetHashAlgorithm(keyValuePair[1]); break;
						case "-exponent": Cracker.HashFunctionExponent = Int32.Parse(keyValuePair[1]); break;
						case "-execution": break;
						case "-attack": attackMode = (AttackMode)Enum.Parse(typeof(AttackMode), keyValuePair[1], true); break;
						case "-input": ProcessInputFile(keyValuePair[1]); break;
						case "-single":
							if (keyValuePair[1].Length != Cracker.Algorithm.HashSize / 4)
								throw new Exception("Invalid hash size.");

							Cracker.Hashes.Add(ByteArrayHelper.ConvertFromHexString(keyValuePair[1]));
							break;
						case "-buffersize": (Cracker as ParallelHashCracker).WordlistBufferSize = Int32.Parse(keyValuePair[1]); break;

						// Brute-force options
						case "-charset":
							string chrset = keyValuePair[1];

							// Replace
							foreach (KeyValuePair<char, string> kv in new Dictionary<char, string>() {
								{ '1', Charsets.Digits },
								{ 'a', Charsets.LowercaseAlphabet },
								{ 'A', Charsets.UppercaseAlphabet },
								{ '$', Charsets.SpecialCharacters }
							})
							{
								chrset = chrset.Replace(String.Format("${0}", kv.Key), kv.Value);
							}

							charset = chrset;
							attackMode = AttackMode.BruteForce;
							break;
						case "-length":
							string[] lengths = keyValuePair[1].Split('-');

							minLength = Int32.Parse(lengths[0]);
							maxLength = lengths.Length > 1 ? Int32.Parse(lengths[1]) : minLength;
							break;
						case "-startfrom": startFrom = keyValuePair[1]; break;

						// Wordlist options
						case "-wordlist":
							wordlistPath = keyValuePair[1];
							attackMode = AttackMode.Dictionary;
							break;
						case "-position": initialWordlistPosition = Int32.Parse(keyValuePair[1]); break;
						
						case "-lowercase": Cracker.WordTransforms.Add(new ToLowercaseWordTransform()); break;
						case "-uppercase": Cracker.WordTransforms.Add(new ToUppercaseWordTransform()); break;
						case "-reverse": Cracker.WordTransforms.Add(new ReverseWordTransform()); break;
						case "-prefix": Cracker.WordTransforms.Add(new AddPrefixWordTransform(keyValuePair[1])); break;
						case "-postfix": Cracker.WordTransforms.Add(new AddPostfixWordTransform(keyValuePair[1])); break;

						// Unkown argument
						default: throw new Exception(String.Format("Unkown argument: {0}", keyValuePair));
					}
				}
			}
			catch (Exception ex)
			{
				Console.WriteLine("Error: {0}", ex.Message);
				return;
			}

			// Set event handlers
			Cracker.AttackStarted += new EventHandler<AttackStartedEventArgs>(Cracker_AttackStarted);
			Cracker.CollisionFound += new EventHandler<CollisionFoundEventArgs>(Cracker_CollisionFound);
			Cracker.AttackFinished += new EventHandler<AttackFinishedEventArgs>(Cracker_AttackFinished);

			// Show information
			if (Cracker is ParallelHashCracker)
			{
				// Parallel
				Console.WriteLine("Execution mode: parallel");
				Console.WriteLine("Number of cores detected: {0}", Environment.ProcessorCount);

				if (attackMode == AttackMode.Dictionary)
					Console.WriteLine("Wordlist buffer: {0} words", (Cracker as ParallelHashCracker).WordlistBufferSize);
			}
			else
			{
				// Sequential
				Console.WriteLine("Execution mode: sequential");
			}
			Console.WriteLine("Loaded {0} hashes", Cracker.Hashes.Count);
			Console.WriteLine();
			LoadedHashes = Cracker.Hashes.Count;

			// Start cracking
			watch = new Stopwatch();

			switch (attackMode)
			{
				case AttackMode.BruteForce:
					// Set missing settings
					if (!minLength.HasValue)
						minLength = 0;

					// Validate settings
					if (!maxLength.HasValue)
						throw new Exception("Maximum candidate length must be specified.");

					// Start attack
					if (startFrom != null)
						Cracker.StartBruteForceAttack(charset, minLength.Value, maxLength.Value, startFrom);
					else
						Cracker.StartBruteForceAttack(charset, minLength.Value, maxLength.Value);
					break;

				case AttackMode.Dictionary:
					// Start attack
					if (initialWordlistPosition.HasValue)
						Cracker.StartDictionaryAttack(wordlistPath, initialWordlistPosition.Value);
					else
						Cracker.StartDictionaryAttack(wordlistPath);
					break;
			}

			
			Console.WriteLine();
		}

		#region Settings
		private static AttackMode attackMode;

		private static string charset;

		private static int? minLength, maxLength;

		private static string startFrom;

		private static string wordlistPath;

		private static int? initialWordlistPosition;
		#endregion

		private static Stopwatch watch;

		/// <summary>
		/// Gets or sets hash cracker engine.
		/// </summary>
		public static HashCracker Cracker { get; set; }

		/// <summary>
		/// Gets the number of loaded hashes.
		/// </summary>
		private static int LoadedHashes { get; set; }

		/// <summary>
		/// Gets the number of found collisions.
		/// </summary>
		private static int FoundCollisions { get; set; }

		/// <summary>
		/// Reads all hashes from an input file.
		/// </summary>
		/// <param name="path"></param>
		public static void ProcessInputFile(string path)
		{
			using (StreamReader sr = new StreamReader(path))
			{
				while (!sr.EndOfStream)
				{
					string line = sr.ReadLine();

					foreach (string s in line.Split(':'))
					{
						if (s.Length != Cracker.Algorithm.HashSize / 4)
							continue;

						Cracker.Hashes.Add(ByteArrayHelper.ConvertFromHexString(s));
					}
				}

				sr.Close();
			}
		}

		private static HashAlgorithm GetHashAlgorithm(string algorithm)
		{
			switch (algorithm.ToUpper())
			{
				case "CRC16": return CRC16.Create();
				case "CRC32": return CRC32.Create();
				case "MD5": return MD5.Create();
				case "RIPEMD160": return RIPEMD160.Create();
				case "SHA1": return SHA1.Create();
				case "SHA256": return SHA256.Create();
				case "SHA384": return SHA384.Create();
				case "SHA512": return SHA512.Create();

				default: throw new Exception("Unkown hash algorithm.");
			}
		}

		#region Event handlers
		private static void Cracker_AttackStarted(object sender, AttackStartedEventArgs e)
		{
			Console.WriteLine("Finding collisions...");
			watch.Start();
		}

		private static void Cracker_CollisionFound(object sender, CollisionFoundEventArgs e)
		{
			FoundCollisions++;
			Console.WriteLine("{0} : {1}", ByteArrayHelper.ConvertToHexString(e.Hash), e.Plaintext);
		}

		private static void Cracker_AttackFinished(object sender, AttackFinishedEventArgs e)
		{
			watch.Stop();
			Console.WriteLine("Elapsed time: {0:F} seconds, guesses: {1}, average speed: {2} c/s", watch.Elapsed.TotalSeconds, e.Guessess, e.Guessess / watch.Elapsed.TotalSeconds);
			Console.WriteLine("Found plaintext: {0} (success rate: {1:P})", FoundCollisions, FoundCollisions / (double)LoadedHashes);
		}
		#endregion
	}
}
